package dominion;

import static dominion.CardTypes.Copper;
import static dominion.CardTypes.Curse;
import static dominion.CardTypes.Gold;
import static dominion.CardTypes.Kingdoms;
import static dominion.CardTypes.Province;
import static dominion.CardTypes.Silver;
import static dominion.CardTypes.Victories;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spaceroots.mantissa.random.MersenneTwister;

public class Dominion {

	Random random = new MersenneTwister(System.currentTimeMillis());

	Logger LOG = LoggerFactory.getLogger(Dominion.class);

	List<Player> players = new ArrayList<Player>();

	private Map<CardTypes, Integer> limits = new EnumMap<CardTypes, Integer>(
			CardTypes.class);

	Player current;
	List<Player> waitings = new ArrayList<Player>();

	public void add(Player player) {
		this.players.add(player);
	}

	public List<Player> players() {
		return this.players;
	}

	public Random random() {
		return this.random;
	}

	public void initialize() {
		shufflePlayers();
		setUpCardLimits();
	}

	private void setUpCardLimits() {
		setLimits(Kingdoms, 10);
		int size = this.players.size();
		limits.put(Copper, 60 - (size * 7));
		limits.put(Silver, 40);
		limits.put(Gold, 30);

		switch (size) {
		case 1:
		case 2: {
			setLimits(Victories, 8);
			limits.put(Curse, 10);
			break;
		}
		case 3: {
			setLimits(Victories, 12);
			limits.put(Curse, 20);
			break;
		}
		case 4: {
			setLimits(Victories, 12);
			limits.put(Curse, 30);
			break;
		}
		default: {
			throw new IllegalStateException();
		}
		}
	}

	private void setLimits(Set<CardTypes> set, int limit) {
		for (CardTypes ct : set) {
			this.limits.put(ct, limit);
		}
	}

	private void shufflePlayers() {
		Collections.shuffle(this.players, this.random);
	}

	public void round() {
		this.waitings.addAll(this.players);
		for (Player p : this.players) {
			if (this.isFinished()) {
				break;
			}
			this.current = p;
			this.waitings.remove(p);
			p.turn(this);
			this.waitings.add(p);
		}
		this.waitings.clear();
	}

	public Player current() {
		return this.current;
	}

	public List<Player> waitings() {
		return this.waitings;
	}

	public boolean isFinished() {
		// solo play simulation
		if (this.players.size() < 2) {
			if (23 < this.players.get(0).victory()) {
				return true;
			}
		}

		if (this.limits.get(Province) < 1) {
			return true;
		}

		Set<CardTypes> set = EnumSet.allOf(CardTypes.class);
		int kinds = 0;
		for (CardTypes ct : set) {
			if (this.limits.get(ct) < 1) {
				kinds++;
			}
			if (2 < kinds) {
				return true;
			}
		}
		return false;
	}

	public List<Player> vpRank() {
		List<Player> list = new ArrayList<Player>(this.players);
		Collections.sort(list, new Comparator<Player>() {
			public int compare(Player l, Player r) {
				return r.victory() - l.victory();
			}
		});
		return list;
	}

	public int limit(CardTypes ct) {
		return this.limits.get(ct);
	}

	public boolean buy(Player buyer, Card card) {
		boolean result = false;
		int i = this.limits.get(card.getType());
		if (0 < i) {
			this.limits.put(card.getType(), --i);
			buyer.gain(card);
			result = true;
		}
		LOG.info("{} try to buy {} success ? {}", new Object[] { buyer.name(),
				card, result });
		return result;
	}

	@Override
	public String toString() {
		StringBuilder stb = new StringBuilder();
		for (Player p : this.players) {
			stb.append("[");
			stb.append(p.name());
			stb.append(":");
			stb.append(p.toString());
			stb.append("]");
		}
		return stb.toString();
	}
}
